package com.panopset.io;

import static com.panopset.Util.*;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.channels.FileLock;

/**
 * Use of this class to write to a file will ensure that nobody else
 * locks it while you are writing.
 *
 * @author Karl Dinwiddie
 *
 */
public class LockedWriter {
    private final File file;
    private final boolean append;

    private BufferedWriter writer;
    private Writer wrappedWriter;
    private FileLock lock;

    /**
     * LockedWriter constructor.
     * @param outFile File to be written out to.
     * @param appendFile Set to true if you want to append text.
     */
    public LockedWriter(File outFile, boolean appendFile) {
        this.file = outFile;
        this.append = appendFile;
        init();
    }

    /**
     * LockedWriter will not append, if the file exists it will be overwritten.
     * @param outFile File to be written out to.
     */
    public LockedWriter(File outFile) {
        this(outFile, false);
        init();
    }

    /**
     * LockedWriter will not append, if the file exists it will be overwritten.
     * @param string Full path of file you want to write out to.
     */
    public LockedWriter(String string) {
        this(new File(string), false);
        init();
    }

    /**
     *
     * @return File associated with this LockedWriter
     */
    public File getFile() {
        return this.file;
    }
    /**
     * Delete the file associated with this LockedWriter
     */
    public void deleteFile() {
        delete(this.file);
    }

    public void close() {
        if (this.lock != null) {
            try {
                this.lock.release();
            } catch(IOException e) {
                log(e);
            }
        }
        try {
            if (this.wrappedWriter != null) {
                this.wrappedWriter.flush();
            }
            this.writer.flush();
            if (this.wrappedWriter != null) {
                this.wrappedWriter.close();
            }
            this.writer.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Write a String, then append a standard line feed/carriage return.
     * @param s
     */
    public void writeln(String s) {
        try {
            this.writer.write(s);
            this.writer.write(DOS_RTN);
        } catch (IOException e) {
            log(e);
        }
    }

    public void write(int c) {
        try {
            this.writer.write(c);
        } catch (IOException e) {
            log(e);
        }
    }

    public void write(char[] cbuf) {
        try {
            this.writer.write(cbuf);
        } catch (IOException e) {
            log(e);
        }
    }

    public void write(char[] cbuf, int off, int len) {
        try {
            this.writer.write(cbuf, off, len);
        } catch (IOException e) {
            log(e);
        }
    }

    public void write(String str) {
        try {
            this.writer.write(str);
        } catch (IOException e) {
            log(e);
        }
    }

    public void write(String s, int off, int len) {
        try {
            this.writer.write(s, off, len);
        } catch (IOException e) {
            log(e);
        }
    }

    /**
     *
     * @return Writer associated with this LockedWriter
     */
    public BufferedWriter getWriter() {
        return this.writer;
    }

    /**
     *
     * @param writer
     */
    private void setWriter(Writer writer) {
        if (writer instanceof BufferedWriter) {
            this.writer = (BufferedWriter) writer;
        } else {
            this.writer = new BufferedWriter(this.wrappedWriter = writer);
        }
    }

    /**
     * Perform all initialization.
     */
    private void init() {
        try {
            File d = this.file.getParentFile();
            mkdirs(d);
            FileOutputStream stream = new FileOutputStream(this.file, append);
            this.lock = stream.getChannel().tryLock();
            setWriter(new OutputStreamWriter(stream));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
